home *** CD-ROM | disk | FTP | other *** search
/ PCGUIA 117 / PC Guia 117.iso / Software / Produtividade / Software2 / Product4 / Setup.exe / drupal-4.6.0 / includes / menu.inc < prev    next >
Encoding:
Text File  |  2005-04-07  |  32.2 KB  |  1,043 lines

  1. <?php
  2. /* $Id: menu.inc,v 1.79 2005/04/07 20:00:48 dries Exp $ */
  3.  
  4. /**
  5.  * @file
  6.  * API for the Drupal menu system.
  7.  */
  8.  
  9. /**
  10.  * @defgroup menu Menu system
  11.  * @{
  12.  * Define the navigation menus, and route page requests to code based on URLs.
  13.  *
  14.  * The Drupal menu system drives both the navigation system from a user
  15.  * perspective and the callback system that Drupal uses to respond to URLs
  16.  * passed from the browser. For this reason, a good understanding of the
  17.  * menu system is fundamental to the creation of complex modules.
  18.  *
  19.  * Drupal's menu system follows a simple hierarchy defined by paths.
  20.  * Implementations of hook_menu() define menu items and assign them to
  21.  * paths (which should be unique). The menu system aggregates these items
  22.  * and determines the menu hierarchy from the paths. For example, if the
  23.  * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system
  24.  * would form the structure:
  25.  * - a
  26.  *   - a/b
  27.  *     - a/b/c/d
  28.  *     - a/b/h
  29.  * - e
  30.  * - f/g
  31.  * Note that the number of elements in the path does not necessarily
  32.  * determine the depth of the menu item in the tree.
  33.  *
  34.  * When responding to a page request, the menu system looks to see if the
  35.  * path requested by the browser is registered as a menu item with a
  36.  * callback. If not, the system searches up the menu tree for the most
  37.  * complete match with a callback it can find. If the path a/b/i is
  38.  * requested in the tree above, the callback for a/b would be used.
  39.  *
  40.  * The found callback function is called with any arguments specified in
  41.  * the "callback arguments" attribute of its menu item. After these
  42.  * arguments, any remaining components of the path are appended as further
  43.  * arguments. In this way, the callback for a/b above could respond to a
  44.  * request for a/b/i differently than a request for a/b/j.
  45.  *
  46.  * For an illustration of this process, see page_example.module.
  47.  *
  48.  * Access to the callback functions is also protected by the menu system.
  49.  * The "access" attribute of each menu item is checked as the search for a
  50.  * callback proceeds. If this attribute is TRUE, then access is granted; if
  51.  * FALSE, then access is denied. The first found "access" attribute
  52.  * determines the accessibility of the target. Menu items may omit this
  53.  * attribute to use the value provided by an ancestor item.
  54.  *
  55.  * In the default Drupal interface, you will notice many links rendered as
  56.  * tabs. These are known in the menu system as "local tasks", and they are
  57.  * rendered as tabs by default, though other presentations are possible.
  58.  * Local tasks function just as other menu items in most respects. It is
  59.  * convention that the names of these tasks should be short verbs if
  60.  * possible. In addition, a "default" local task should be provided for
  61.  * each set. When visiting a local task's parent menu item, the default
  62.  * local task will be rendered as if it is selected; this provides for a
  63.  * normal tab user experience. This default task is special in that it
  64.  * links not to its provided path, but to its parent item's path instead.
  65.  * The default task's path is only used to place it appropriately in the
  66.  * menu hierarchy.
  67.  */
  68.  
  69. /**
  70.  * @name Menu flags
  71.  * @{
  72.  * Flags for use in the "type" attribute of menu items.
  73.  */
  74.  
  75. define('MENU_IS_ROOT', 0x0001);
  76. define('MENU_VISIBLE_IN_TREE', 0x0002);
  77. define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004);
  78. define('MENU_VISIBLE_IF_HAS_CHILDREN', 0x0008);
  79. define('MENU_MODIFIABLE_BY_ADMIN', 0x0010);
  80. define('MENU_MODIFIED_BY_ADMIN', 0x0020);
  81. define('MENU_CREATED_BY_ADMIN', 0x0040);
  82. define('MENU_IS_LOCAL_TASK', 0x0080);
  83. define('MENU_EXPANDED', 0x0100);
  84. define('MENU_LINKS_TO_PARENT', 0x0200);
  85.  
  86. /**
  87.  * @} End of "Menu flags".
  88.  */
  89.  
  90. /**
  91.  * @name Menu item types
  92.  * @{
  93.  * Menu item definitions provide one of these constants, which are shortcuts for
  94.  * combinations of the above flags.
  95.  */
  96.  
  97. /**
  98.  * Normal menu items show up in the menu tree and can be moved/hidden by
  99.  * the administrator. Use this for most menu items. It is the default value if
  100.  * no menu item type is specified.
  101.  */
  102. define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB | MENU_MODIFIABLE_BY_ADMIN);
  103.  
  104. /**
  105.  * Item groupings are used for pages like "node/add" that simply list
  106.  * subpages to visit. They are distinguished from other pages in that they will
  107.  * disappear from the menu if no subpages exist.
  108.  */
  109. define('MENU_ITEM_GROUPING', MENU_VISIBLE_IF_HAS_CHILDREN | MENU_VISIBLE_IN_BREADCRUMB | MENU_MODIFIABLE_BY_ADMIN);
  110.  
  111. /**
  112.  * Callbacks simply register a path so that the correct function is fired
  113.  * when the URL is accessed. They are not shown in the menu.
  114.  */
  115. define('MENU_CALLBACK', MENU_VISIBLE_IN_BREADCRUMB);
  116.  
  117. /**
  118.  * Dynamic menu items change frequently, and so should not be stored in the
  119.  * database for administrative customization.
  120.  */
  121. define('MENU_DYNAMIC_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB);
  122.  
  123. /**
  124.  * Modules may "suggest" menu items that the administrator may enable. They act
  125.  * just as callbacks do until enabled, at which time they act like normal items.
  126.  */
  127. define('MENU_SUGGESTED_ITEM', MENU_MODIFIABLE_BY_ADMIN);
  128.  
  129. /**
  130.  * Local tasks are rendered as tabs by default. Use this for menu items that
  131.  * describe actions to be performed on their parent item. An example is the path
  132.  * "node/52/edit", which performs the "edit" task on "node/52".
  133.  */
  134. define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK);
  135.  
  136. /**
  137.  * Every set of local tasks should provide one "default" task, that links to the
  138.  * same path as its parent when clicked.
  139.  */
  140. define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT);
  141.  
  142. /**
  143.  * Custom items are those defined by the administrator. Reserved for internal
  144.  * use; do not return from hook_menu() implementations.
  145.  */
  146. define('MENU_CUSTOM_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB | MENU_CREATED_BY_ADMIN | MENU_MODIFIABLE_BY_ADMIN);
  147.  
  148. /**
  149.  * Custom menus are those defined by the administrator. Reserved for internal
  150.  * use; do not return from hook_menu() implementations.
  151.  */
  152. define('MENU_CUSTOM_MENU', MENU_IS_ROOT | MENU_VISIBLE_IN_TREE | MENU_CREATED_BY_ADMIN | MENU_MODIFIABLE_BY_ADMIN);
  153.  
  154. /**
  155.  * @} End of "Menu item types".
  156.  */
  157.  
  158. /**
  159.  * @name Menu status codes
  160.  * @{
  161.  * Status codes for menu callbacks.
  162.  */
  163.  
  164. define('MENU_FOUND', 1);
  165. define('MENU_NOT_FOUND', 2);
  166. define('MENU_ACCESS_DENIED', 3);
  167.  
  168. /**
  169.  * @} End of "Menu status codes".
  170.  */
  171.  
  172. /**
  173.  * Return the menu data structure.
  174.  *
  175.  * The returned structure contains much information that is useful only
  176.  * internally in the menu system. External modules are likely to need only
  177.  * the ['visible'] element of the returned array. All menu items that are
  178.  * accessible to the current user and not hidden will be present here, so
  179.  * modules and themes can use this structure to build their own representations
  180.  * of the menu.
  181.  *
  182.  * $menu['visible'] will contain an associative array, the keys of which
  183.  * are menu IDs. The values of this array are themselves associative arrays,
  184.  * with the following key-value pairs defined:
  185.  * - 'title' - The displayed title of the menu or menu item. It will already
  186.  *   have been translated by the locale system.
  187.  * - 'description' - The description (link title attribute) of the menu item.
  188.  *   It will already have been translated by the locale system.
  189.  * - 'path' - The Drupal path to the menu item. A link to a particular item
  190.  *   can thus be constructed with
  191.  *   l($item['title'], $item['path'], array('title' => $item['description'])).
  192.  * - 'children' - A linear list of the menu ID's of this item's children.
  193.  *
  194.  * Menu ID 0 is the "root" of the menu. The children of this item are the
  195.  * menus themselves (they will have no associated path). Menu ID 1 will
  196.  * always be one of these children; it is the default "Navigation" menu.
  197.  */
  198. function menu_get_menu() {
  199.   global $_menu;
  200.   global $user;
  201.   global $locale;
  202.  
  203.   if (!isset($_menu['items'])) {
  204.     // _menu_build() may indirectly call this function, so prevent infinite loops.
  205.     $_menu['items'] = array();
  206.  
  207.     $cid = "menu:$user->uid:$locale";
  208.     if ($cached = cache_get($cid)) {
  209.       $_menu = unserialize($cached->data);
  210.     }
  211.     else {
  212.       _menu_build();
  213.       // Cache the menu structure for this user, to expire after one day.
  214.       cache_set($cid, serialize($_menu), time() + (60 * 60 * 24));
  215.     }
  216.  
  217.     // Make sure items that cannot be cached are added.
  218.     _menu_append_contextual_items();
  219.   }
  220.  
  221.   return $_menu;
  222. }
  223.  
  224. /**
  225.  * Return the local task tree.
  226.  *
  227.  * Unlike the rest of the menu structure, the local task tree cannot be cached
  228.  * nor determined too early in the page request, because the user's current
  229.  * location may be changed by a menu_set_location() call, and the tasks shown
  230.  * (just as the breadcrumb trail) need to reflect the changed location.
  231.  */
  232. function menu_get_local_tasks() {
  233.   global $_menu;
  234.  
  235.   // Don't cache the local task tree, as it varies by location and tasks are
  236.   // allowed to be dynamically determined.
  237.   if (!isset($_menu['local tasks'])) {
  238.     // _menu_build_local_tasks() may indirectly call this function, so prevent
  239.     // infinite loops.
  240.     $_menu['local tasks'] = array();
  241.     $pid = menu_get_active_nontask_item();
  242.     if (!_menu_build_local_tasks($pid)) {
  243.       // If the build returned FALSE, the tasks need not be displayed.
  244.       $_menu['local tasks'][$pid]['children'] = array();
  245.     }
  246.   }
  247.  
  248.   return $_menu['local tasks'];
  249. }
  250.  
  251. /**
  252.  * Change the current menu location of the user.
  253.  *
  254.  * Frequently, modules may want to make a page or node act as if it were
  255.  * in the menu tree somewhere, even though it was not registered in a
  256.  * hook_menu() implementation. If the administrator has rearranged the menu,
  257.  * the newly set location should respect this in the breadcrumb trail and
  258.  * expanded/collapsed status of menu items in the tree. This function
  259.  * allows this behavior.
  260.  *
  261.  * @param $location
  262.  *   An array specifying a complete or partial breadcrumb trail for the
  263.  *   new location, in the same format as the return value of hook_menu().
  264.  *   The last element of this array should be the new location itself.
  265.  *
  266.  * This function will set the new breadcrumb trail to the passed-in value,
  267.  * but if any elements of this trail are visible in the site tree, the
  268.  * trail will be "spliced in" to the existing site navigation at that point.
  269.  */
  270. function menu_set_location($location) {
  271.   global $_menu;
  272.   $temp_id = min(array_keys($_menu['items'])) - 1;
  273.   $prev_id = 0;
  274.  
  275.   foreach (array_reverse($location) as $item) {
  276.     if (isset($_menu['path index'][$item['path']])) {
  277.       $mid = $_menu['path index'][$item['path']];
  278.       if (isset ($_menu['visible'][$mid])) {
  279.         // Splice in the breadcrumb at this location.
  280.         if ($prev_id) {
  281.           $_menu['items'][$prev_id]['pid'] = $mid;
  282.         }
  283.         $prev_id = 0;
  284.         break;
  285.       }
  286.       else {
  287.         // A hidden item; show it, but only temporarily.
  288.         $_menu['items'][$mid]['type'] |= MENU_VISIBLE_IN_BREADCRUMB;
  289.         if ($prev_id) {
  290.           $_menu['items'][$prev_id]['pid'] = $mid;
  291.         }
  292.         $prev_id = $mid;
  293.       }
  294.     }
  295.     else {
  296.       $item['type'] |= MENU_VISIBLE_IN_BREADCRUMB;
  297.       if ($prev_id) {
  298.         $_menu['items'][$prev_id]['pid'] = $temp_id;
  299.       }
  300.       $_menu['items'][$temp_id] = $item;
  301.       $_menu['path index'][$item['path']] = $temp_id;
  302.  
  303.       $prev_id = $temp_id;
  304.       $temp_id--;
  305.     }
  306.   }
  307.  
  308.   if ($prev_id) {
  309.     // Didn't find a home, so attach this to the main navigation menu.
  310.     $_menu['items'][$prev_id]['pid'] = 1;
  311.   }
  312.  
  313.   $final_item = array_pop($location);
  314.   menu_set_active_item($final_item['path']);
  315. }
  316.  
  317. /**
  318.  * Execute the handler associated with the active menu item.
  319.  *
  320.  * This is called early in the page request. The active menu item is at
  321.  * this point determined exclusively by the URL. The handler that is called
  322.  * here may, as a side effect, change the active menu item so that later
  323.  * menu functions (that display the menus and breadcrumbs, for example)
  324.  * act as if the user were in a different location on the site.
  325.  */
  326. function menu_execute_active_handler() {
  327.   $menu = menu_get_menu();
  328.  
  329.   // Determine the menu item containing the callback.
  330.   $path = $_GET['q'];
  331.   while ($path && (!array_key_exists($path, $menu['path index']) || empty($menu['items'][$menu['path index'][$path]]['callback']))) {
  332.     $path = substr($path, 0, strrpos($path, '/'));
  333.   }
  334.   if (!array_key_exists($path, $menu['path index'])) {
  335.     return MENU_NOT_FOUND;
  336.   }
  337.   $mid = $menu['path index'][$path];
  338.  
  339.   if (empty($menu['items'][$mid]['callback'])) {
  340.     return MENU_NOT_FOUND;
  341.   }
  342.  
  343.   if (!_menu_item_is_accessible(menu_get_active_item())) {
  344.     return MENU_ACCESS_DENIED;
  345.   }
  346.  
  347.   // We found one, and are allowed to execute it.
  348.   $arguments = array_key_exists('callback arguments', $menu['items'][$mid]) ? $menu['items'][$mid]['callback arguments'] : array();
  349.   $arg = substr($_GET['q'], strlen($menu['items'][$mid]['path']) + 1);
  350.   if (strlen($arg)) {
  351.     $arguments = array_merge($arguments, explode('/', $arg));
  352.   }
  353.  
  354.   call_user_func_array($menu['items'][$mid]['callback'], $arguments);
  355.   return MENU_FOUND;
  356. }
  357.  
  358. /**
  359.  * Returns the ID of the active menu item.
  360.  */
  361. function menu_get_active_item() {
  362.   return menu_set_active_item();
  363. }
  364.  
  365. /**
  366.  * Sets the path of the active menu item.
  367.  */
  368. function menu_set_active_item($path = NULL) {
  369.   static $stored_mid;
  370.   $menu = menu_get_menu();
  371.  
  372.   if (is_null($stored_mid) || !empty($path)) {
  373.     if (empty($path)) {
  374.       $path = $_GET['q'];
  375.     }
  376.     else {
  377.       $_GET['q'] = $path;
  378.     }
  379.  
  380.     while ($path && !array_key_exists($path, $menu['path index'])) {
  381.       $path = substr($path, 0, strrpos($path, '/'));
  382.     }
  383.     $stored_mid = array_key_exists($path, $menu['path index']) ? $menu['path index'][$path] : 0;
  384.  
  385.     // Search for default local tasks to activate instead of this item.
  386.     $continue = TRUE;
  387.     while ($continue) {
  388.       $continue = FALSE;
  389.       if (array_key_exists('children', $menu['items'][$stored_mid])) {
  390.         foreach ($menu['items'][$stored_mid]['children'] as $cid) {
  391.           if ($menu['items'][$cid]['type'] & MENU_LINKS_TO_PARENT) {
  392.             $stored_mid = $cid;
  393.             $continue = TRUE;
  394.           }
  395.         }
  396.       }
  397.     }
  398.   }
  399.  
  400.   return $stored_mid;
  401. }
  402.  
  403. /**
  404.  * Returns the ID of the current menu item or, if the current item is a
  405.  * local task, the menu item to which this task is attached.
  406.  */
  407. function menu_get_active_nontask_item() {
  408.   $menu = menu_get_menu();
  409.   $mid = menu_get_active_item();
  410.  
  411.   // Find the first non-task item:
  412.   while ($mid && ($menu['items'][$mid]['type'] & MENU_IS_LOCAL_TASK)) {
  413.     $mid = $menu['items'][$mid]['pid'];
  414.   }
  415.  
  416.   if ($mid) {
  417.     return $mid;
  418.   }
  419. }
  420.  
  421. /**
  422.  * Returns the title of the active menu item.
  423.  */
  424. function menu_get_active_title() {
  425.   $menu = menu_get_menu();
  426.  
  427.   if ($mid = menu_get_active_nontask_item()) {
  428.     return $menu['items'][$mid]['title'];
  429.   }
  430. }
  431.  
  432. /**
  433.  * Returns the help associated with the active menu item.
  434.  */
  435. function menu_get_active_help() {
  436.   $path = $_GET['q'];
  437.   $output = '';
  438.  
  439.   if (!_menu_item_is_accessible(menu_get_active_item())) {
  440.     // Don't return help text for areas the user cannot access.
  441.     return;
  442.   }
  443.  
  444.   foreach (module_list() as $name) {
  445.     if (module_hook($name, 'help')) {
  446.       if ($temp = module_invoke($name, 'help', $path)) {
  447.         $output .= $temp . "\n";
  448.       }
  449.       if (module_hook('help', 'page')) {
  450.         if (substr($path, 0, 6) == "admin/") {
  451.           if (module_invoke($name, 'help', 'admin/help#' . substr($path, 6))) {
  452.             $output .= theme("more_help_link", url('admin/help/' . substr($path, 6)));
  453.           }
  454.         }
  455.       }
  456.     }
  457.   }
  458.   return $output;
  459. }
  460.  
  461. /**
  462.  * Returns an array of rendered menu items in the active breadcrumb trail.
  463.  */
  464. function menu_get_active_breadcrumb() {
  465.   $menu = menu_get_menu();
  466.  
  467.   $links[] = l(t('Home'), '');
  468.  
  469.   $trail = _menu_get_active_trail();
  470.   foreach ($trail as $mid) {
  471.     if ($menu['items'][$mid]['type'] & MENU_VISIBLE_IN_BREADCRUMB) {
  472.       $links[] = menu_item_link($mid);
  473.     }
  474.   }
  475.  
  476.   // The last item in the trail is the page title; don't display it here.
  477.   array_pop($links);
  478.  
  479.   return $links;
  480. }
  481.  
  482. /**
  483.  * Returns true when the menu item is in the active trail.
  484.  */
  485. function menu_in_active_trail($mid) {
  486.   $trail = _menu_get_active_trail();
  487.  
  488.   return in_array($mid, $trail);
  489. }
  490.  
  491. /**
  492.  * Populate the database representation of the menu.
  493.  *
  494.  * This need only be called at the start of pages that modify the menu.
  495.  */
  496. function menu_rebuild() {
  497.   // Clear the page cache, so that changed menus are reflected for anonymous users.
  498.   cache_clear_all();
  499.   // Also clear the menu cache.
  500.   cache_clear_all('menu:', TRUE);
  501.  
  502.   _menu_build();
  503.  
  504.   if (module_exist('menu')) {
  505.     $menu = menu_get_menu();
  506.  
  507.     $new_items = array();
  508.     foreach ($menu['items'] as $mid => $item) {
  509.       if ($mid < 0 && ($item['type'] & MENU_MODIFIABLE_BY_ADMIN)) {
  510.         $new_mid = db_next_id('{menu}_mid');
  511.         // Check explicitly for mid 1. If the database was improperly prefixed,
  512.         // this would cause a nasty infinite loop.
  513.         // TODO: have automatic prefixing through an installer to prevent this.
  514.         if ($new_mid == 1) {
  515.           $new_mid = db_next_id('{menu}_mid');
  516.         }
  517.         if (isset($new_items[$item['pid']])) {
  518.           $new_pid = $new_items[$item['pid']]['mid'];
  519.         }
  520.         else {
  521.           $new_pid = $item['pid'];
  522.         }
  523.  
  524.         // Fix parent IDs for menu items already added.
  525.         if ($item['children']) {
  526.           foreach ($item['children'] as $child) {
  527.             if (isset($new_items[$child])) {
  528.               $new_items[$child]['pid'] = $new_mid;
  529.             }
  530.           }
  531.         }
  532.  
  533.         $new_items[$mid] = array('mid' => $new_mid, 'pid' => $new_pid, 'path' => $item['path'], 'title' => $item['title'], 'description' => array_key_exists('description', $item) ? $item['description'] : '', 'weight' => $item['weight'], 'type' => $item['type']);
  534.       }
  535.     }
  536.  
  537.     if (count($new_items)) {
  538.       foreach ($new_items as $item) {
  539.         db_query('INSERT INTO {menu} (mid, pid, path, title, description, weight, type) VALUES (%d, %d, \'%s\', \'%s\', \'%s\', %d, %d)', $item['mid'], $item['pid'], $item['path'], $item['title'], $item['description'], $item['weight'], $item['type']);
  540.       }
  541.  
  542.       // Rebuild the menu to account for the changes.
  543.       _menu_build();
  544.     }
  545.   }
  546. }
  547.  
  548. /**
  549.  * Generate the HTML for a menu tree.
  550.  *
  551.  * @param $pid
  552.  *   The parent id of the menu.
  553.  *
  554.  * @ingroup themeable
  555.  */
  556. function theme_menu_tree($pid = 1) {
  557.   if ($tree = menu_tree($pid)) {
  558.     return "\n<ul>\n". $tree ."\n</ul>\n";
  559.   }
  560. }
  561.  
  562. /**
  563.  * Returns a rendered menu tree.
  564.  *
  565.  * @param $pid
  566.  *   The parent id of the menu.
  567.  */
  568. function menu_tree($pid = 1) {
  569.   $menu = menu_get_menu();
  570.   $output = '';
  571.  
  572.   if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) {
  573.     foreach ($menu['visible'][$pid]['children'] as $mid) {
  574.       $output .= theme('menu_item', $mid, menu_in_active_trail($mid) || ($menu['visible'][$mid]['type'] & MENU_EXPANDED) ? theme('menu_tree', $mid) : '', count($menu['visible'][$mid]['children']) == 0);
  575.     }
  576.   }
  577.  
  578.   return $output;
  579. }
  580.  
  581. /**
  582.  * Generate the HTML output for a single menu item.
  583.  *
  584.  * @param $mid
  585.  *   The menu id of the item.
  586.  * @param $children
  587.  *   A string containing any rendered child items of this menu.
  588.  * @param $leaf
  589.  *   A boolean indicating whether this menu item is a leaf.
  590.  *
  591.  * @ingroup themeable
  592.  */
  593. function theme_menu_item($mid, $children = '', $leaf = TRUE) {
  594.   return '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. menu_item_link($mid) . $children ."</li>\n";
  595. }
  596.  
  597. /**
  598.  * Generate the HTML representing a given menu item ID.
  599.  *
  600.  * @param $item
  601.  *   The menu item to render.
  602.  * @param $link_mid
  603.  *   The menu item which should be used to find the correct path.
  604.  *
  605.  * @ingroup themeable
  606.  */
  607. function theme_menu_item_link($item, $link_item) {
  608.   return l($item['title'], $link_item['path'], array_key_exists('description', $item) ? array('title' => $items['description']) : array());
  609. }
  610.  
  611. /**
  612.  * Returns the rendered link to a menu item.
  613.  *
  614.  * @param $mid
  615.  *   The menu item id to render.
  616.  */
  617. function menu_item_link($mid) {
  618.   $menu = menu_get_menu();
  619.  
  620.   $link_mid = $mid;
  621.   while ($menu['items'][$link_mid]['type'] & MENU_LINKS_TO_PARENT) {
  622.     $link_mid = $menu['items'][$link_mid]['pid'];
  623.   }
  624.  
  625.   return theme('menu_item_link', $menu['items'][$mid], $menu['items'][$link_mid]);
  626. }
  627.  
  628. /**
  629.  * Returns the rendered local tasks. The default implementation renders
  630.  * them as tabs.
  631.  *
  632.  * @ingroup themeable
  633.  */
  634. function theme_menu_local_tasks() {
  635.   $output = '';
  636.  
  637.   if ($primary = menu_primary_local_tasks()) {
  638.     $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
  639.   }
  640.   if ($secondary = menu_secondary_local_tasks()) {
  641.     $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
  642.   }
  643.  
  644.   return $output;
  645. }
  646.  
  647. /**
  648.  * Returns the rendered HTML of the primary local tasks.
  649.  */
  650. function menu_primary_local_tasks() {
  651.   $local_tasks = menu_get_local_tasks();
  652.   $pid = menu_get_active_nontask_item();
  653.   $output = '';
  654.  
  655.   if (count($local_tasks[$pid]['children'])) {
  656.     foreach ($local_tasks[$pid]['children'] as $mid) {
  657.       $output .= theme('menu_local_task', $mid, menu_in_active_trail($mid), TRUE);
  658.     }
  659.   }
  660.  
  661.   return $output;
  662. }
  663.  
  664. /**
  665.  * Returns the rendered HTML of the secondary local tasks.
  666.  */
  667. function menu_secondary_local_tasks() {
  668.   $local_tasks = menu_get_local_tasks();
  669.   $pid = menu_get_active_nontask_item();
  670.   $output = '';
  671.  
  672.   if (count($local_tasks[$pid]['children'])) {
  673.     foreach ($local_tasks[$pid]['children'] as $mid) {
  674.       if (menu_in_active_trail($mid) && count($local_tasks[$mid]['children']) > 1) {
  675.         foreach ($local_tasks[$mid]['children'] as $cid) {
  676.           $output .= theme('menu_local_task', $cid, menu_in_active_trail($cid), FALSE);
  677.         }
  678.       }
  679.     }
  680.   }
  681.  
  682.   return $output;
  683. }
  684.  
  685. /**
  686.  * Generate the HTML representing a given menu item ID as a tab.
  687.  *
  688.  * @param $mid
  689.  *   The menu ID to render.
  690.  * @param $active
  691.  *   Whether this tab or a subtab is the active menu item.
  692.  * @param $primary
  693.  *   Whether this tab is a primary tab or a subtab.
  694.  *
  695.  * @ingroup themeable
  696.  */
  697. function theme_menu_local_task($mid, $active, $primary) {
  698.   if ($active) {
  699.     return '<li class="active">'. menu_item_link($mid) ."</li>\n";
  700.   }
  701.   else {
  702.     return '<li>'. menu_item_link($mid) ."</li>\n";
  703.   }
  704. }
  705.  
  706. /**
  707.  * @} End of "defgroup menu".
  708.  */
  709.  
  710. /**
  711.  * Returns an array with the menu items that lead to the current menu item.
  712.  */
  713. function _menu_get_active_trail() {
  714.   static $trail;
  715.  
  716.   if (!isset($trail)) {
  717.     $menu = menu_get_menu();
  718.  
  719.     $trail = array();
  720.  
  721.     $mid = menu_get_active_item();
  722.  
  723.     // Follow the parents up the chain to get the trail.
  724.     while ($mid && $menu['items'][$mid]) {
  725.       array_unshift($trail, $mid);
  726.       $mid = $menu['items'][$mid]['pid'];
  727.     }
  728.   }
  729.   return $trail;
  730. }
  731.  
  732. /**
  733.  * Comparator routine for use in sorting menu items.
  734.  */
  735. function _menu_sort($a, $b) {
  736.   $menu = menu_get_menu();
  737.  
  738.   $a = &$menu['items'][$a];
  739.   $b = &$menu['items'][$b];
  740.  
  741.   return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1));
  742. }
  743.  
  744. /**
  745.  * Build the menu by querying both modules and the database.
  746.  */
  747. function _menu_build() {
  748.   global $_menu;
  749.   global $user;
  750.  
  751.   // Start from a clean slate.
  752.   $_menu = array();
  753.  
  754.   $_menu['path index'] = array();
  755.   // Set up items array, including default "Navigation" menu.
  756.   $_menu['items'] = array(
  757.     0 => array('path' => '', 'title' => '', 'type' => MENU_IS_ROOT),
  758.     1 => array('pid' => 0, 'path' => '', 'title' => t('Navigation'), 'weight' => -50, 'access' => TRUE, 'type' => MENU_IS_ROOT | MENU_VISIBLE_IN_TREE)
  759.     );
  760.  
  761.   // Build a sequential list of all menu items.
  762.   $menu_item_list = module_invoke_all('menu', TRUE);
  763.  
  764.   // Menu items not in the DB get temporary negative IDs.
  765.   $temp_mid = -1;
  766.  
  767.   foreach ($menu_item_list as $item) {
  768.     if (!array_key_exists('path', $item)) {
  769.       $item['path'] = '';
  770.     }
  771.     if (!array_key_exists('type', $item)) {
  772.       $item['type'] = MENU_NORMAL_ITEM;
  773.     }
  774.     if (!array_key_exists('weight', $item)) {
  775.       $item['weight'] = 0;
  776.     }
  777.     $mid = $temp_mid;
  778.     if (array_key_exists($item['path'], $_menu['path index'])) {
  779.       // Newer menu items overwrite older ones.
  780.       unset($_menu['items'][$_menu['path index'][$item['path']]]);
  781.     }
  782.     $_menu['items'][$mid] = $item;
  783.     $_menu['path index'][$item['path']] = $mid;
  784.  
  785.     $temp_mid--;
  786.   }
  787.  
  788.   // Now fetch items from the DB, reassigning menu IDs as needed.
  789.   if (module_exist('menu')) {
  790.     $result = db_query('SELECT * FROM {menu} ORDER BY mid ASC');
  791.     while ($item = db_fetch_object($result)) {
  792.       // Handle URL aliases if entered in menu administration.
  793.       $item->path = drupal_get_normal_path($item->path);
  794.       if (array_key_exists($item->path, $_menu['path index'])) {
  795.         // The path is already declared.
  796.         $old_mid = $_menu['path index'][$item->path];
  797.         if ($old_mid < 0) {
  798.           // It had a temporary ID, so use a permanent one.
  799.           $_menu['items'][$item->mid] = $_menu['items'][$old_mid];
  800.           unset($_menu['items'][$old_mid]);
  801.           $_menu['path index'][$item->path] = $item->mid;
  802.         }
  803.         else {
  804.           // It has a permanent ID. Only replace with non-custom menu items.
  805.           if ($item->type & MENU_CREATED_BY_ADMIN) {
  806.             $_menu['items'][$item->mid] = array('path' => $item->path, 'access' => TRUE, 'callback' => '');
  807.           }
  808.           else {
  809.             // Leave the old item around as a shortcut to this one.
  810.             $_menu['items'][$item->mid] = $_menu['items'][$old_mid];
  811.             $_menu['path index'][$item->path] = $item->mid;
  812.           }
  813.         }
  814.       }
  815.       else {
  816.         // The path was not declared, so this is a custom item or an orphaned one.
  817.         if ($item->type & MENU_CREATED_BY_ADMIN) {
  818.           $_menu['items'][$item->mid] = array('path' => $item->path, 'access' => TRUE, 'callback' => '');
  819.           if (!empty($item->path)) {
  820.             $_menu['path index'][$item->path] = $item->mid;
  821.           }
  822.         }
  823.       }
  824.  
  825.       // If the administrator has changed the item, reflect the change.
  826.       if ($item->type & MENU_MODIFIED_BY_ADMIN) {
  827.         $_menu['items'][$item->mid]['title'] = $item->title;
  828.         $_menu['items'][$item->mid]['description'] = $item->description;
  829.         $_menu['items'][$item->mid]['pid'] = $item->pid;
  830.         $_menu['items'][$item->mid]['weight'] = $item->weight;
  831.         $_menu['items'][$item->mid]['type'] = $item->type;
  832.       }
  833.     }
  834.   }
  835.  
  836.   // Associate parent and child menu items.
  837.   _menu_find_parents($_menu['items']);
  838.  
  839.   // Prepare to display trees to the user as required.
  840.   _menu_build_visible_tree();
  841. }
  842.  
  843. /**
  844.  * Determine whether the given menu item is accessible to the current user.
  845.  *
  846.  * Use this instead of just checking the "access" property of a menu item
  847.  * to properly handle items with fall-through semantics.
  848.  */
  849. function _menu_item_is_accessible($mid) {
  850.   $menu = menu_get_menu();
  851.  
  852.   // Follow the path up to find the first "access" attribute.
  853.   $path = $menu['items'][$mid]['path'];
  854.   while ($path && (!array_key_exists($path, $menu['path index']) || !array_key_exists('access', $menu['items'][$menu['path index'][$path]]))) {
  855.     $path = substr($path, 0, strrpos($path, '/'));
  856.   }
  857.   if (empty($path)) {
  858.     return FALSE;
  859.   }
  860.   return $menu['items'][$menu['path index'][$path]]['access'];
  861. }
  862.  
  863. /**
  864.  * Find all visible items in the menu tree, for ease in displaying to user.
  865.  *
  866.  * Since this is only for display, we only need title, path, and children
  867.  * for each item.
  868.  */
  869. function _menu_build_visible_tree($pid = 0) {
  870.   global $_menu;
  871.  
  872.   if (isset($_menu['items'][$pid])) {
  873.     $parent = $_menu['items'][$pid];
  874.  
  875.     $children = array();
  876.     if (array_key_exists('children', $parent)) {
  877.       usort($parent['children'], '_menu_sort');
  878.       foreach ($parent['children'] as $mid) {
  879.         $children = array_merge($children, _menu_build_visible_tree($mid));
  880.       }
  881.     }
  882.     $visible = ($parent['type'] & MENU_VISIBLE_IN_TREE) ||
  883.       ($parent['type'] & MENU_VISIBLE_IF_HAS_CHILDREN && count($children) > 0);
  884.     $allowed = _menu_item_is_accessible($pid);
  885.  
  886.     if (($parent['type'] & MENU_IS_ROOT) || ($visible && $allowed)) {
  887.       $_menu['visible'][$pid] = array('title' => $parent['title'], 'path' => $parent['path'], 'children' => $children, 'type' => $parent['type']);
  888.       foreach ($children as $mid) {
  889.         $_menu['visible'][$mid]['pid'] = $pid;
  890.       }
  891.       return array($pid);
  892.     }
  893.     else {
  894.       return $children;
  895.     }
  896.   }
  897.  
  898.   return array();
  899. }
  900.  
  901. /**
  902.  * Account for menu items that are only defined at certain paths, so will not
  903.  * be cached.
  904.  *
  905.  * We don't support the full range of menu item options for these menu items. We
  906.  * don't support MENU_VISIBLE_IF_HAS_CHILDREN, and we require parent items to be
  907.  * declared before their children.
  908.  */
  909. function _menu_append_contextual_items() {
  910.   global $_menu;
  911.  
  912.   // Build a sequential list of all menu items.
  913.   $menu_item_list = module_invoke_all('menu', FALSE);
  914.  
  915.   // Menu items not in the DB get temporary negative IDs.
  916.   $temp_mid = min(array_keys($_menu['items'])) - 1;
  917.   $new_items = array();
  918.  
  919.   foreach ($menu_item_list as $item) {
  920.     if (array_key_exists($item['path'], $_menu['path index'])) {
  921.       // The menu item already exists, so just add appropriate callback information.
  922.       $mid = $_menu['path index'][$item['path']];
  923.  
  924.       $_menu['items'][$mid]['access'] = $item['access'];
  925.       $_menu['items'][$mid]['callback'] = $item['callback'];
  926.       $_menu['items'][$mid]['callback arguments'] = $item['callback arguments'];
  927.     }
  928.     else {
  929.       if (!array_key_exists('path', $item)) {
  930.         $item['path'] = '';
  931.       }
  932.       if (!array_key_exists('type', $item)) {
  933.         $item['type'] = MENU_NORMAL_ITEM;
  934.       }
  935.       if (!array_key_exists('weight', $item)) {
  936.         $item['weight'] = 0;
  937.       }
  938.       $_menu['items'][$temp_mid] = $item;
  939.       $_menu['path index'][$item['path']] = $temp_mid;
  940.       $new_items[$temp_mid] = $item;
  941.       $temp_mid--;
  942.     }
  943.   }
  944.  
  945.   // Establish parent-child relationships.
  946.   _menu_find_parents($new_items);
  947.  
  948.   // Add new items to the visible tree if necessary.
  949.   foreach ($new_items as $mid => $item) {
  950.     $item = $_menu['items'][$mid];
  951.     if (($item['type'] & MENU_VISIBLE_IN_TREE) && _menu_item_is_accessible($mid)) {
  952.       $pid = $item['pid'];
  953.       while ($pid && !array_key_exists($pid, $_menu['visible'])) {
  954.         $pid = $_menu['items'][$pid]['pid'];
  955.       }
  956.       $_menu['visible'][$mid] = array('title' => $item['title'], 'path' => $item['path'], 'pid' => $pid);
  957.       $_menu['visible'][$pid]['children'][] = $mid;
  958.       usort($_menu['visible'][$pid]['children'], '_menu_sort');
  959.     }
  960.   }
  961. }
  962.  
  963. /**
  964.  * Establish parent-child relationships.
  965.  */
  966. function _menu_find_parents(&$items) {
  967.   global $_menu;
  968.  
  969.   foreach ($items as $mid => $item) {
  970.     if (!isset($item['pid'])) {
  971.       // Parent's location has not been customized, so figure it out using the path.
  972.       $parent = $item['path'];
  973.       do {
  974.         $parent = substr($parent, 0, strrpos($parent, '/'));
  975.       }
  976.       while ($parent && !array_key_exists($parent, $_menu['path index']));
  977.  
  978.       $pid = $parent ? $_menu['path index'][$parent] : 1;
  979.       $_menu['items'][$mid]['pid'] = $pid;
  980.     }
  981.     else {
  982.       $pid = $item['pid'];
  983.     }
  984.  
  985.     // Don't make root a child of itself.
  986.     if ($mid) {
  987.       if (isset ($_menu['items'][$pid])) {
  988.         $_menu['items'][$pid]['children'][] = $mid;
  989.       }
  990.       else {
  991.         // If parent is missing, it is a menu item that used to be defined
  992.         // but is no longer. Default to a root-level "Navigation" menu item.
  993.         $_menu['items'][1]['children'][] = $mid;
  994.       }
  995.     }
  996.   }
  997. }
  998.  
  999. /**
  1000.  * Find all the items in the current local task tree.
  1001.  *
  1002.  * Since this is only for display, we only need title, path, and children
  1003.  * for each item.
  1004.  *
  1005.  * At the close of this function, $_menu['local tasks'] is populated with the
  1006.  * menu items in the local task tree.
  1007.  *
  1008.  * @return
  1009.  *   TRUE if the local task tree is forked. It does not need to be displayed
  1010.  *   otherwise.
  1011.  */
  1012. function _menu_build_local_tasks($pid) {
  1013.   global $_menu;
  1014.  
  1015.   $forked = FALSE;
  1016.  
  1017.   if (isset($_menu['items'][$pid])) {
  1018.     $parent = $_menu['items'][$pid];
  1019.  
  1020.     $children = array();
  1021.     if (array_key_exists('children', $parent)) {
  1022.       foreach ($parent['children'] as $mid) {
  1023.         if (($_menu['items'][$mid]['type'] & MENU_IS_LOCAL_TASK) && _menu_item_is_accessible($mid)) {
  1024.           $children[] = $mid;
  1025.           // Beware short-circuiting || operator!
  1026.           $forked = _menu_build_local_tasks($mid) || $forked;
  1027.         }
  1028.       }
  1029.     }
  1030.     usort($children, '_menu_sort');
  1031.     $forked = $forked || count($children) > 1;
  1032.  
  1033.     $_menu['local tasks'][$pid] = array('title' => $parent['title'], 'path' => $parent['path'], 'children' => $children);
  1034.     foreach ($children as $mid) {
  1035.       $_menu['local tasks'][$mid]['pid'] = $pid;
  1036.     }
  1037.   }
  1038.  
  1039.   return $forked;
  1040. }
  1041.  
  1042. ?>
  1043.